﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using System.Configuration;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;

using NLog;

namespace Interface
{
    #region Exception
    public class ExceptionHandlerBehaviorElement : BehaviorExtensionElement
    {
        [ConfigurationProperty("exceptionPolicy", IsRequired = true)]
        public string ExceptionPolicyName
        {
            get { return (string)this["exceptionPolicy"]; }
            set { this["exceptionPolicy"] = value; }
        }
        public override Type BehaviorType
        {
            get { return typeof(ExceptionHandlerBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new ExceptionHandlerBehavior(ExceptionPolicyName);
        }
    }

    public class ExceptionHandlerBehavior : IServiceBehavior
    {
        public string ExceptionPolicyName { get; private set; }
        public ExceptionHandlerBehavior(string exceptionPolicyName)
        {
            this.ExceptionPolicyName = exceptionPolicyName;
        }

        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
            {
                channelDispatcher.ErrorHandlers.Add(new ServiceErrorHandler(ExceptionPolicyName));
            }
        }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {

        }
    }

    public class ServiceErrorHandler : IErrorHandler
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public string ExceptionPolicyName { get; private set; }
        public ServiceErrorHandler(string exceptionPolicyName)
        {
            this.ExceptionPolicyName = exceptionPolicyName;
        }
        public bool HandleError(Exception error)
        {
            return false;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            if (typeof(FaultException).IsInstanceOfType(error))
            {
                return;
            }
            logger.Error("[Service Error]:{0}", ExceptionPolicyName);
            fault = Message.CreateMessage(version,
                MessageFault.CreateFault(new FaultCode("FaultCode"), new FaultReason(error.Message)),
                "http://www.chinascopefinancial.com/fault");
        }
    }
    #endregion

    #region Message
    public class MessageInspectorBehaviorElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(MessageInspectorEndpointBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new MessageInspectorEndpointBehavior();
        }
    }
    public class MessageInspectorEndpointBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new ClientMessageInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new ServiceMessageInspector());
        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }
    }
    public class ServiceMessageInspector : IDispatchMessageInspector
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            logger.Info("[Service:AfterReceiveRequest]");
            logger.Info(request.ToString());
            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            logger.Info("[Service:BeforeSendReply]");
            logger.Info(reply.ToString());
        }
    }
    public class ClientMessageInspector : IClientMessageInspector
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            logger.Info("[Client:AfterReceiveReply]");
            logger.Info(reply.ToString());
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            logger.Info("[Client:BeforeSendRequest]");
            logger.Info(request.ToString());
            return channel.GetType();
        }
    }
    #endregion

    #region Invoke
    public class ServiceActionInterceptBehaviorElement : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(ActionInterceptBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new ActionInterceptBehavior();
        }
    }

    public class ActionInterceptBehavior : IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
            
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            foreach (var operation in endpoint.Contract.Operations)
            {
                operation.Behaviors.Add(new ActionOperationBehavior());
            }
        }

        public void Validate(ServiceEndpoint endpoint)
        {
            
        }
    }
    public class ActionOperationBehavior : IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
            
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
            
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            var old = dispatchOperation.Invoker;
            dispatchOperation.Invoker = new ActionOperationInvoker(old);
        }

        public void Validate(OperationDescription operationDescription)
        {
            
        }
    }
    public class ActionOperationInvoker : IOperationInvoker
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private IOperationInvoker invoker;
        public ActionOperationInvoker(IOperationInvoker invoker)
        {
            this.invoker = invoker;
        }
        public object[] AllocateInputs()
        {
            return invoker.AllocateInputs();
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            object returnedValue = null;
            Exception exception = null;
            try
            {
                returnedValue = invoker.Invoke(instance, inputs, out outputs);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }

            PostInvoke(inputs, returnedValue, outputs, exception);

            return returnedValue;
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return invoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            object returnValue = null;
            Exception exception = null;
            try
            {
                returnValue = invoker.InvokeEnd(instance, out outputs, result);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }

            PostInvoke(result.AsyncState as object[], returnValue, outputs, exception);

            return returnValue;
        }

        public bool IsSynchronous
        {
            get { return invoker.IsSynchronous; }
        }
        protected virtual void PostInvoke(object[] inputs, object returnedValue, object[] outputs, Exception exception)
        {
            logger.Info("[Service Invoke] Return:{0} Output:{1}", returnedValue.ToString(), string.Join(",", outputs));
        }
    }
    #endregion

    #region 客户端代理
    public static class ServiceLocator
    {
        public static T Create<T>(string endpointConfigName) where T : class
        {
            return (T)(new ServiceRealProxy<T>(endpointConfigName)).GetTransparentProxy();
        }
    }


    internal class ServiceRealProxy<T> : RealProxy where T : class
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private string endpointConfigName;
        public ServiceRealProxy(string endpointConfigName)
            : base(typeof(T))
        {
            this.endpointConfigName = endpointConfigName;
        }

        public override IMessage Invoke(IMessage msg)
        {
            using (var client = CreateChannelFactory(endpointConfigName))
            {
                var channel = client.CreateChannel();
                IMethodCallMessage methodCall = (IMethodCallMessage)msg;
                IMethodReturnMessage methodReturn = null;
                object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
                methodCall.Args.CopyTo(copiedArgs, 0);

                bool isSuccessful = false;
                try
                {
                    logger.Info("[Client Begin Invoke]:{0}", methodCall.MethodBase.Name);
                    logger.Info("[Client Args]:{0}", string.Join(";", methodCall.Args));
                    object returnValue = methodCall.MethodBase.Invoke(channel, copiedArgs);
                    methodReturn = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, methodCall.LogicalCallContext, methodCall);
                    isSuccessful = true;
                }
                catch (Exception ex)
                {
                    methodReturn = new ReturnMessage(ex, methodCall);
                    logger.Error("Client Invoke Exception");
                }
                finally
                {
                    logger.Info("[Client End Invoke]{0}:{1}，执行结果:{2}", typeof(T).FullName, methodCall.MethodBase.Name, isSuccessful ? "成功" : "失败");
                }
                return methodReturn;
            }
        }

        static Dictionary<string, ChannelFactory> channelFactories =
            new Dictionary<string, ChannelFactory>();
        internal static ChannelFactory<T> CreateChannelFactory(string endpointConfigName)
        {
            if (channelFactories.ContainsKey(endpointConfigName))
            {
                return channelFactories[endpointConfigName] as ChannelFactory<T>;
            }
            lock (channelFactories)
            {
                if (channelFactories.ContainsKey(endpointConfigName))
                {
                    return channelFactories[endpointConfigName] as ChannelFactory<T>;
                }
                ChannelFactory<T> channelFactory = new ChannelFactory<T>(endpointConfigName);
                channelFactory.Open();
                channelFactories[endpointConfigName] = channelFactory;

                logger.Info("[Client Start] Contract:{0};endpointConfigName:{1}", typeof(T).FullName, endpointConfigName);

                return channelFactory;
            }
        }

    }
    #endregion

    public static class ServiceHostFactory
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public static ServiceHost CreateServiceHost(Type serviceType)
        {
            var typeName = serviceType.FullName;
            var serviceHost = new ServiceHost(serviceType);
            serviceHost.Opened += (sender, e) =>
                {
                    logger.Info("Service Start:{0}", typeName);
                };
            return serviceHost;
        }
    }

}
